View Javadoc
1   package org.apache.maven.surefire.junitcore.pc;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.surefire.junitcore.JUnitCoreParameters;
23  import org.apache.maven.surefire.testset.TestSetFailedException;
24  import org.junit.runner.Description;
25  
26  import java.util.Arrays;
27  import java.util.Collection;
28  import java.util.Iterator;
29  
30  /**
31   * An algorithm which configures {@link ParallelComputer} with allocated thread resources by given
32   * {@link org.apache.maven.surefire.junitcore.JUnitCoreParameters}.
33   * The <code>AbstractSurefireMojo</code> has to provide correct combinations of thread-counts and <em>parallel</em>.
34   *
35   * @author Tibor Digana (tibor17)
36   * @see org.apache.maven.surefire.junitcore.pc.ParallelComputerBuilder
37   * @since 2.16
38   */
39  final class ParallelComputerUtil
40  {
41      private static final Collection<Description> UNUSED_DESCRIPTIONS =
42              Arrays.asList( null, Description.createSuiteDescription( "null" ), Description.TEST_MECHANISM,
43                      Description.EMPTY );
44  
45      private static int availableProcessors = Runtime.getRuntime().availableProcessors();
46  
47      private ParallelComputerUtil()
48      {
49          throw new IllegalStateException( "Suppresses calling constructor, ensuring non-instantiability." );
50      }
51  
52      /*
53      * For testing purposes.
54      */
55      static void overrideAvailableProcessors( int availableProcessors )
56      {
57          ParallelComputerUtil.availableProcessors = availableProcessors;
58      }
59  
60      /*
61      * For testing purposes.
62      */
63      static void setDefaultAvailableProcessors()
64      {
65          ParallelComputerUtil.availableProcessors = Runtime.getRuntime().availableProcessors();
66      }
67  
68      static Concurrency resolveConcurrency( JUnitCoreParameters params, RunnerCounter counts )
69          throws TestSetFailedException
70      {
71          if ( !params.isParallelismSelected() )
72          {
73              throw new TestSetFailedException( "Unspecified parameter '" + JUnitCoreParameters.PARALLEL_KEY + "'." );
74          }
75  
76          if ( !params.isUseUnlimitedThreads() && !hasThreadCount( params ) && !hasThreadCounts( params ) )
77          {
78              throw new TestSetFailedException( "Unspecified thread-count(s). "
79                                                    + "See the parameters "
80                                                    + JUnitCoreParameters.USEUNLIMITEDTHREADS_KEY + ", "
81                                                    + JUnitCoreParameters.THREADCOUNT_KEY + ", "
82                                                    + JUnitCoreParameters.THREADCOUNTSUITES_KEY + ", "
83                                                    + JUnitCoreParameters.THREADCOUNTCLASSES_KEY + ", "
84                                                    + JUnitCoreParameters.THREADCOUNTMETHODS_KEY + "." );
85          }
86  
87          if ( params.isUseUnlimitedThreads() )
88          {
89              return concurrencyForUnlimitedThreads( params );
90          }
91          else if ( hasThreadCount( params ) )
92          {
93              if ( hasThreadCounts( params ) )
94              {
95                  return isLeafUnspecified( params )
96                      ? concurrencyFromAllThreadCountsButUnspecifiedLeafCount( params, counts )
97                      : concurrencyFromAllThreadCounts( params );
98              }
99              else
100             {
101                 return estimateConcurrency( params, counts );
102             }
103         }
104         else
105         {
106             return concurrencyFromThreadCounts( params );
107         }
108     }
109 
110     static boolean isUnusedDescription( Description examined )
111     {
112         if ( UNUSED_DESCRIPTIONS.contains( examined ) )
113         {
114             return true;
115         }
116         else
117         {
118             // UNUSED_DESCRIPTIONS ensures that "examined" cannot be null
119             for ( Description unused : UNUSED_DESCRIPTIONS )
120             {
121                 if ( unused != null && unused.getDisplayName().equals( examined.getDisplayName() ) )
122                 {
123                     return true;
124                 }
125             }
126             return false;
127         }
128     }
129 
130     static void removeUnusedDescriptions( Collection<Description> examined )
131     {
132         for ( Iterator<Description> it = examined.iterator(); it.hasNext(); )
133         {
134             if ( isUnusedDescription( it.next() ) )
135             {
136                 it.remove();
137             }
138         }
139     }
140 
141     private static Concurrency concurrencyForUnlimitedThreads( JUnitCoreParameters params )
142     {
143         Concurrency concurrency = new Concurrency();
144         concurrency.suites = params.isParallelSuites() ? threadCountSuites( params ) : 0;
145         concurrency.classes = params.isParallelClasses() ? threadCountClasses( params ) : 0;
146         concurrency.methods = params.isParallelMethods() ? threadCountMethods( params ) : 0;
147         concurrency.capacity = Integer.MAX_VALUE;
148         return concurrency;
149     }
150 
151     private static Concurrency estimateConcurrency( JUnitCoreParameters params, RunnerCounter counts )
152     {
153         final Concurrency concurrency = new Concurrency();
154         final int parallelEntities = countParallelEntities( params );
155         concurrency.capacity = multiplyByCoreCount( params, params.getThreadCount() );
156         if ( parallelEntities == 1 || counts == null || counts.classes == 0 )
157         {
158             // Estimate parallel thread counts.
159             double ratio = 1d / parallelEntities;
160             int threads = multiplyByCoreCount( params, ratio * params.getThreadCount() );
161             concurrency.suites = params.isParallelSuites() ? minSuites( threads, counts ) : 0;
162             concurrency.classes = params.isParallelClasses() ? minClasses( threads, counts ) : 0;
163             concurrency.methods = params.isParallelMethods() ? minMethods( threads, counts ) : 0;
164             if ( parallelEntities == 1 )
165             {
166                 concurrency.capacity = 0;
167             }
168             else
169             {
170                 adjustLeaf( params, concurrency );
171             }
172         }
173         else
174         {
175             // Try to allocate suites+classes+methods within threadCount,
176             concurrency.suites = params.isParallelSuites() ? toNonNegative( counts.suites ) : 0;
177             concurrency.classes = params.isParallelClasses() ? toNonNegative( counts.classes ) : 0;
178             concurrency.methods =
179                 params.isParallelMethods() ? toNonNegative( Math.ceil( counts.methods / (double) counts.classes ) ) : 0;
180             double sum = toNonNegative( concurrency.suites + concurrency.classes + concurrency.methods );
181             if ( concurrency.capacity < sum && sum != 0 )
182             {
183                 // otherwise allocate them using the weighting factor < 1.
184                 double weight = concurrency.capacity / sum;
185                 concurrency.suites *= weight;
186                 concurrency.classes *= weight;
187                 concurrency.methods *= weight;
188             }
189             adjustLeaf( params, concurrency );
190         }
191         return concurrency;
192     }
193 
194     private static Concurrency concurrencyFromAllThreadCountsButUnspecifiedLeafCount( JUnitCoreParameters params,
195                                                                                       RunnerCounter counts )
196     {
197         Concurrency concurrency = new Concurrency();
198         concurrency.suites = params.isParallelSuites() ? params.getThreadCountSuites() : 0;
199         concurrency.suites = params.isParallelSuites() ? multiplyByCoreCount( params, concurrency.suites ) : 0;
200         concurrency.classes = params.isParallelClasses() ? params.getThreadCountClasses() : 0;
201         concurrency.classes = params.isParallelClasses() ? multiplyByCoreCount( params, concurrency.classes ) : 0;
202         concurrency.methods = params.isParallelMethods() ? params.getThreadCountMethods() : 0;
203         concurrency.methods = params.isParallelMethods() ? multiplyByCoreCount( params, concurrency.methods ) : 0;
204         concurrency.capacity = multiplyByCoreCount( params, params.getThreadCount() );
205 
206         if ( counts != null )
207         {
208             concurrency.suites = toNonNegative( Math.min( concurrency.suites, counts.suites ) );
209             concurrency.classes = toNonNegative( Math.min( concurrency.classes, counts.classes ) );
210         }
211 
212         setLeafInfinite( params, concurrency );
213 
214         return concurrency;
215     }
216 
217     private static Concurrency concurrencyFromAllThreadCounts( JUnitCoreParameters params )
218     {
219         Concurrency concurrency = new Concurrency();
220         concurrency.suites = params.isParallelSuites() ? params.getThreadCountSuites() : 0;
221         concurrency.classes = params.isParallelClasses() ? params.getThreadCountClasses() : 0;
222         concurrency.methods = params.isParallelMethods() ? params.getThreadCountMethods() : 0;
223         concurrency.capacity = params.getThreadCount();
224         double all = sumThreadCounts( concurrency );
225 
226         concurrency.suites = params.isParallelSuites() ? multiplyByCoreCount( params, concurrency.capacity * (
227             concurrency.suites / all ) ) : 0;
228 
229         concurrency.classes = params.isParallelClasses() ? multiplyByCoreCount( params, concurrency.capacity * (
230             concurrency.classes / all ) ) : 0;
231 
232         concurrency.methods = params.isParallelMethods() ? multiplyByCoreCount( params, concurrency.capacity * (
233             concurrency.methods / all ) ) : 0;
234 
235         concurrency.capacity = multiplyByCoreCount( params, concurrency.capacity );
236         adjustPrecisionInLeaf( params, concurrency );
237         return concurrency;
238     }
239 
240     private static Concurrency concurrencyFromThreadCounts( JUnitCoreParameters params )
241     {
242         Concurrency concurrency = new Concurrency();
243         concurrency.suites = params.isParallelSuites() ? threadCountSuites( params ) : 0;
244         concurrency.classes = params.isParallelClasses() ? threadCountClasses( params ) : 0;
245         concurrency.methods = params.isParallelMethods() ? threadCountMethods( params ) : 0;
246         concurrency.capacity = toNonNegative( sumThreadCounts( concurrency ) );
247         return concurrency;
248     }
249 
250     private static int countParallelEntities( JUnitCoreParameters params )
251     {
252         int count = 0;
253         if ( params.isParallelSuites() )
254         {
255             count++;
256         }
257 
258         if ( params.isParallelClasses() )
259         {
260             count++;
261         }
262 
263         if ( params.isParallelMethods() )
264         {
265             count++;
266         }
267         return count;
268     }
269 
270     private static void adjustPrecisionInLeaf( JUnitCoreParameters params, Concurrency concurrency )
271     {
272         if ( params.isParallelMethods() )
273         {
274             concurrency.methods = concurrency.capacity - concurrency.suites - concurrency.classes;
275         }
276         else if ( params.isParallelClasses() )
277         {
278             concurrency.classes = concurrency.capacity - concurrency.suites;
279         }
280     }
281 
282     private static void adjustLeaf( JUnitCoreParameters params, Concurrency concurrency )
283     {
284         if ( params.isParallelMethods() )
285         {
286             concurrency.methods = Integer.MAX_VALUE;
287         }
288         else if ( params.isParallelClasses() )
289         {
290             concurrency.classes = Integer.MAX_VALUE;
291         }
292     }
293 
294     private static void setLeafInfinite( JUnitCoreParameters params, Concurrency concurrency )
295     {
296         if ( params.isParallelMethods() )
297         {
298             concurrency.methods = Integer.MAX_VALUE;
299         }
300         else if ( params.isParallelClasses() )
301         {
302             concurrency.classes = Integer.MAX_VALUE;
303         }
304         else if ( params.isParallelSuites() )
305         {
306             concurrency.suites = Integer.MAX_VALUE;
307         }
308     }
309 
310     private static boolean isLeafUnspecified( JUnitCoreParameters params )
311     {
312         int maskOfParallel = params.isParallelSuites() ? 4 : 0;
313         maskOfParallel |= params.isParallelClasses() ? 2 : 0;
314         maskOfParallel |= params.isParallelMethods() ? 1 : 0;
315 
316         int maskOfConcurrency = params.getThreadCountSuites() > 0 ? 4 : 0;
317         maskOfConcurrency |= params.getThreadCountClasses() > 0 ? 2 : 0;
318         maskOfConcurrency |= params.getThreadCountMethods() > 0 ? 1 : 0;
319 
320         maskOfConcurrency &= maskOfParallel;
321 
322         int leaf = Integer.lowestOneBit( maskOfParallel );
323         return maskOfConcurrency == maskOfParallel - leaf;
324     }
325 
326     private static double sumThreadCounts( Concurrency concurrency )
327     {
328         double sum = concurrency.suites;
329         sum += concurrency.classes;
330         sum += concurrency.methods;
331         return sum;
332     }
333 
334     private static boolean hasThreadCounts( JUnitCoreParameters jUnitCoreParameters )
335     {
336         return ( jUnitCoreParameters.isParallelSuites() && jUnitCoreParameters.getThreadCountSuites() > 0 )
337             || ( jUnitCoreParameters.isParallelClasses() && jUnitCoreParameters.getThreadCountClasses() > 0 )
338             || ( jUnitCoreParameters.isParallelMethods() && jUnitCoreParameters.getThreadCountMethods() > 0 );
339     }
340 
341     private static boolean hasThreadCount( JUnitCoreParameters jUnitCoreParameters )
342     {
343         return jUnitCoreParameters.getThreadCount() > 0;
344     }
345 
346     private static int threadCountMethods( JUnitCoreParameters jUnitCoreParameters )
347     {
348         return multiplyByCoreCount( jUnitCoreParameters, jUnitCoreParameters.getThreadCountMethods() );
349     }
350 
351     private static int threadCountClasses( JUnitCoreParameters jUnitCoreParameters )
352     {
353         return multiplyByCoreCount( jUnitCoreParameters, jUnitCoreParameters.getThreadCountClasses() );
354     }
355 
356     private static int threadCountSuites( JUnitCoreParameters jUnitCoreParameters )
357     {
358         return multiplyByCoreCount( jUnitCoreParameters, jUnitCoreParameters.getThreadCountSuites() );
359     }
360 
361     private static int multiplyByCoreCount( JUnitCoreParameters jUnitCoreParameters, double threadsPerCore )
362     {
363         double numberOfThreads =
364             jUnitCoreParameters.isPerCoreThreadCount() ? threadsPerCore * (double) availableProcessors : threadsPerCore;
365 
366         return numberOfThreads > 0 ? toNonNegative( numberOfThreads ) : Integer.MAX_VALUE;
367     }
368 
369     private static int minSuites( int threads, RunnerCounter counts )
370     {
371         long count = counts == null ? Integer.MAX_VALUE : counts.suites;
372         return Math.min( threads, toNonNegative( count ) );
373     }
374 
375     private static int minClasses( int threads, RunnerCounter counts )
376     {
377         long count = counts == null ? Integer.MAX_VALUE : counts.classes;
378         return Math.min( threads, toNonNegative( count ) );
379     }
380 
381     private static int minMethods( int threads, RunnerCounter counts )
382     {
383         long count = counts == null ? Integer.MAX_VALUE : counts.methods;
384         return Math.min( threads, toNonNegative( count ) );
385     }
386 
387     private static int toNonNegative( long num )
388     {
389         return (int) Math.min( num > 0 ? num : 0, Integer.MAX_VALUE );
390     }
391 
392     private static int toNonNegative( double num )
393     {
394         return (int) Math.min( num > 0 ? num : 0, Integer.MAX_VALUE );
395     }
396 }